<?php

class Basket {
	
	private $basket_id;
	/**
	 * Basket reference
	 *
	 * @var int
	 **/
	private $reference;
	
	private $lines;
	
	public $shipping_band = 1;
	
	/**
	 * Zend_Db object
	 *
	 * @var object
	 * @access private
	 **/
	private $db;
	
	/**
	 * Cache so we don't have to do more database than we need to
	 * 
	 * @var array
	 * @access private
	 **/
	public $cache;
	
	public function __construct() {
		global $db;
		$this->db = $db;
		
		if (isset($_COOKIE['basket_reference'])) {
			if ($this->basket_id = $db->fetchOne("SELECT id FROM shop_baskets WHERE reference=?", $_COOKIE['basket_reference'])) {
				$this->recoverBasket($_COOKIE['basket_reference']);
			}  else {
				$this->setupBasket();
			}
		} else {
			$this->setupBasket();
		}
		
	}
	
	public function destroy() {
		$this->db->query('DELETE FROM shop_baskets_lines_simpleattributes WHERE line_id IN (SELECT id FROM shop_baskets_lines WHERE basket_id=?)', $this->basket_id);
		$this->db->delete('shop_baskets_lines', $this->db->quoteInto('basket_id=?', $this->basket_id));
		$this->db->delete('shop_baskets', $this->db->quoteInto('id=?', $this->basket_id));
	}
	
	public function export() {
		$ships_in_min = null;
		$ships_in_max = null;
		
		foreach($this->lines() as $line) {
			if (!is_numeric($ships_in_min) or is_numeric($line['product']['ships_in_min'] and $ships_in_min > $line['product']['ships_in_min'])) {
				$ships_in_min = $line['product']['ships_in_min'];
			}
			if (!is_numeric($ships_in_max) or is_numeric($line['product']['ships_in_max'] and $ships_in_max < $line['product']['ships_in_max'])) {
				$ships_in_max = $line['product']['ships_in_max'];
			}
		}
		return array(
			'reference'        => $this->getBasketReference(),
			'lines'            => $this->lines(),
			'total_price_incl' => $this->total_incl(),
			'total_price_excl' => $this->total_excl(),
			'total_vat'        => $this->total_incl() - $this->total_excl(),
			'shipments'        => $this->shipments(),
			'shipping'         => $this->shippingOptions($this->lines()),
			'ships_in_min'     => $ships_in_min,
			'ships_in_max'     => $ships_in_max
		);
	}
	
	public function total_incl() {
		$lines = $this->lines();
		$total = 0;
		foreach($lines as $line) {
			$total += $line['product']['total_price_incl'];
		}
		return $total;
	}
	
	public function total_excl() {
		$lines = $this->lines();
		$total = 0;
		foreach($lines as $line) {
			$total += $line['product']['total_price_excl'];
		}
		return $total;
	}
	/**
	 * Add product to basket
	 * 
	 * @param class $product Product object
	 *
	 * @return void
	 * @access public
	 **/
	public function add($product) {
		if ($line_id = $this->db->fetchOne("SELECT id FROM shop_baskets_lines WHERE basket_id=? AND product_id=? AND simpleattributes_hash=?", array($this->basket_id, $product->getId(), $product->simpleAttributesHash()))) {
			$this->db->update('shop_baskets_lines', array(
				'quantity' => new Zend_Db_Expr('quantity + ' . $product->getQuantity())
			), $this->db->quoteInto('id=?', $line_id));
		} else {
			$this->db->insert('shop_baskets_lines', array(
				'basket_id'             => $this->basket_id,
				'product_id'            => $product->getId(),
				'quantity'              => $product->getQuantity(),
				'simpleattributes_hash' => $product->simpleAttributesHash()
			));
			$line_id = $this->db->lastInsertId();
			$simpleAttributes = $product->simpleAttributes();
			foreach($simpleAttributes as $simpleAttribute) {
				$this->db->insert('shop_baskets_lines_simpleattributes', array(
					'line_id'            => $line_id,
					'simpleattribute_id' => $simpleAttribute['id'],
					'value'              => $simpleAttribute['value']
				));
			}
		}
		$this->cache = array();
		$this->touch();
		return $line_id;
	}
	
	/**
	 * Set the quantity on a product line
	 *
	 * @return int $quantity
	 * @access public
	 **/
	public function set($line_id, $quantity) {
		if ($quantity == 0) {
			$this->del($line_id);
		} else {
			$product = new Product($this->db->fetchOne("SELECT product_id FROM shop_baskets_lines WHERE basket_id=? AND id=?", array($this->basket_id, $line_id)));
			$simpleAttributes = $product->simpleAttributes();
			foreach($simpleAttributes as $title => $value) {
				#print_r($title); print_r($value);
				$product->setSimpleAttribute($title, $value['value']);
			}
			$product->setQuantity($quantity);
			$this->db->query("UPDATE shop_baskets_lines SET quantity=? WHERE basket_id=? AND id=?", array($product->getQuantity(), $this->basket_id, $line_id));
		}
		$this->cache = array();
		$this->touch();
		return $quantity == 0 ? 0 : $product->getQuantity();
	}
	
	/**
	 * Delete a line from the basket
	 *
	 * @return void
	 * @access public
	 **/
	public function del($line_id) {
		$this->db->query("DELETE FROM shop_baskets_lines_simpleattributes WHERE line_id IN (SELECT id FROM shop_baskets_lines WHERE basket_id=? AND id=?)", array($this->basket_id, $line_id));
		$this->db->query("DELETE FROM shop_baskets_lines WHERE basket_id=? AND id=?", array($this->basket_id, $line_id));
		$this->cache = array();
		$this->touch();
	}
	
	/**
	 * Get the lines in the basket
	 *
	 * @return array $lines
	 * @access public
	 **/
	public function lines() {
		if (isset($this->cache['lines'])) return $this->cache['lines'];
		$lines = $this->db->fetchAll("SELECT * FROM shop_baskets_lines WHERE basket_id=?", $this->basket_id);
		foreach($lines as $i => $line) {
			$lines[$i]['product'] = new Product($line['product_id']);
			$lines[$i]['product']->setQuantity($line['quantity']);
			$simpleAttributes = $this->db->fetchAll("SELECT * FROM shop_baskets_lines_simpleattributes WHERE line_id=?", $line['id']);
			foreach($simpleAttributes as $simpleAttribute) {
				$lines[$i]['product']->setSimpleAttribute($simpleAttribute['simpleattribute_id'], $simpleAttribute['value']);
			}
			$lines[$i]['product'] = $lines[$i]['product']->export(true);
		}
		$this->cache['lines'] = $lines;
		return $lines;
	}
	
	/**
	 * Take the lines in the basket, and process them based on shipping times into separate shipments
	 *
	 * @return array $shipments
	 * @access private
	 **/
	public function shipments() {
		global $env;
		
		$lines = $this->lines();
		$shipments = array();
		foreach($lines as $i => $line) {
			$key = $line['product']['ships_in_min'] ? $line['product']['ships_in_min'] : '-';
			if (!isset($shipments[$$key])) {
				$shipments[$key] = array('lines' => array(), 'ships_in_min' => $line['product']['ships_in_min']);
			}
			$shipments[$key]['lines'][] = $line;
		}
		ksort($shipments);
		
		foreach($shipments as $shipment1 => $lines1) {
			foreach($shipments as $shipment2 => $lines) { if ($shipment2 != $shipment1) {
				if ($shipment2 > $shipment1 and $shipment2 - $shipment1 < $env['shop']['split_shipment_threshold']) {
					$shipments[$shipment2]['lines'] = array_merge($shipments[$shipment1]['lines'], $shipments[$shipment2]['lines']);
					unset($shipments[$shipment1]);
					sort($shipments[$shipment2]['lines']);
				}
			} }
		}
		foreach($shipments as $i => $shipment) {
			foreach($shipments[$i]['lines'] as $line) {
				if (!is_numeric($shipments[$i]['ships_in_min']) or is_numeric($line['product']['ships_in_min'] and $shipments[$i]['ships_in_min'] > $line['product']['ships_in_min'])) {
					$shipment['ships_in_min'] = $line['product']['ships_in_min'];
				}
				if (!is_numeric($shipments[$i]['ships_in_max']) or is_numeric($line['product']['ships_in_max'] and $shipments[$i]['ships_in_max'] < $line['product']['ships_in_max'])) {
					$shipment['ships_in_max'] = $line['product']['ships_in_max'];
				}
			}
			$shipments[$i] = array_merge($shipment, array('shipping' => $this->shippingOptions($shipment['lines'])));
		}
		
		$shipments = array_merge($shipments);
		
		return $shipments;
	}
	
	/**
	 * Take the integer reference, and split it into a readable form
	 *
	 * @return void
	 * @author Colin Burn-Murdoch
	 **/
	public function getBasketReference() {
		return substr($this->reference, 0, 4) . '-'
		     . substr($this->reference, 4, 4) . '-'
		     . substr($this->reference, 8, 4);
	}
	
	/**
	 * Generate a reference in the form 123412341234 and ensure
	 * it is unique
	 *
	 * @return int $reference
	 * @access private
	 **/
	private function generateBasketReference() {
		$reference = mt_rand(1000,9999) . mt_rand(1000,9999) . mt_rand(1000,9999);
		if ($this->db->fetchOne("SELECT id FROM shop_baskets WHERE reference=?", $reference)) {
			$reference = $this->generateBasketReference();
		}
		return $reference;
	}
	
	/**
	 * Recover an existing basket based on $reference (from cookie)
	 * If the basket doesn't exist, create a new one.
	 *
	 * @return void
	 * @access private
	 **/
	private function recoverBasket($reference) {
		$basket = $this->db->fetchRow("SELECT * FROM shop_baskets WHERE reference=?", $reference);
		
		// delete anything from the basket where the product isn't available to purchase.
		$this->db->query("DELETE FROM shop_baskets_lines WHERE product_id NOT IN (SELECT id FROM shop_products WHERE (active=1 AND deleted=0) OR parent_id IS NOT NULL) AND basket_id=?", $basket['id']);
		
		if ($basket) {
			$this->basket    = $basket;
			$this->reference = $reference;
			#$this->lines     = $this->db->fetchAll("SELECT * FROM shop_baskets_lines WHERE basket_id=?", $basket['id']);
		} else {
			$this->setupBasket();
		}
	}
	
	/**
	 * Set the basket up, creating it if neccessary
	 *
	 * @return void
	 * @access private
	 **/
	private function setupBasket() {
		global $site;
		
		if (isset($_SESSION['login']['user']['id'])) {
			$basket = $this->db->fetchRow("SELECT * FROM shop_baskets WHERE user_id=?", $_SESSION['login']['user']['id']);
		}
		if (isset($basket) and $basket) {
			$this->reference = $basket['reference'];
			$this->basket    = $basket;
			#$this->lines     = $this->db->fetchAll("SELECT * FROM shop_baskets_lines WHERE basket_id=?", $basket['id']);
		} else {
			$this->reference = $this->generateBasketReference();
			$basket = array(
				'site_id'   => $site['id'],
				'user_id'   => (isset($_SESSION['login']) ? $_SESSION['login']['user']['id'] : null),
				'reference' => $this->reference,
				'created'   => new Zend_Db_Expr('NOW()'),
				'updated'   => new Zend_Db_Expr('NOW()')
			);
			$this->db->insert('shop_baskets', $basket);
			$basket['id'] = $this->db->lastInsertId();
			$this->basket = $basket;
			#$this->lines = array();
		}
		setcookie('basket_reference', $this->reference, time()+60*60*24*365, '/', $site['domain_name']);
	}
	
	/**
	 * Mark the basket as updated
	 *
	 * @return void
	 * @access private
	 **/
	private function touch() {
		$this->db->update('shop_baskets', array('updated' => new Zend_Db_Expr('NOW()')), $this->db->quoteInto('id=?', $this->basket_id));
	}
	
	/**
	 * Get the available shipping options based on the given lines
	 *
	 * @return array $shipping_options
	 * @access private
	 **/
	private function shippingOptions($lines) {
		global $site, $env;
		$value  = 0;
		$weight = 0;
		$delivery_products = 0;
		foreach($lines as $line) {
			if ($line['product']['free_shipping'] == 0) {
				$delivery_products++;
				$value  += $line['product']['price'] * $line['quantity'];
				$weight += $line['product']['weight'] * $line['quantity'];
			}
		}
		
		if ($delivery_products == 0) {
			return array(
				array(
					'id'             => 0,
					'shipping_band'  => $this->shipping_band,
					'price'          => 0,
					'title'          => 'No delivery charge',
					'tracking_url'   => '',
					'vat_rate'       => 0,
					'price_incl_vat' => 0,
					'price_excl_vat' => 0,
					'price_vat'      => 0
				)
			);
		}
		
		$sql = "SELECT * FROM shop_shipping_rules r JOIN shop_shipping_types t ON r.shipping_type_id=t.id
			WHERE r.site_id={$site['id']}
			AND (r.shipping_band = {$this->shipping_band} OR r.shipping_band IS NULL)
			AND (minimum_value <= {$value} OR minimum_value IS NULL)
			AND (maximum_value >= {$value} OR maximum_value IS NULL)
			AND (minimum_weight <= {$weight} OR minimum_weight IS NULL)
			AND (maximum_weight >= {$weight} OR maximum_weight IS NULL)
			ORDER BY price ASC
		";
		
		$shipping_options = array_index($this->db->fetchAll($sql), 'id');
		
		foreach($shipping_options as $i => $option) {
			if (isset($env['shop']['vat']) and $env['shop']['vat'] == 'excl') {
				$shipping_options[$i]['price_excl_vat'] = $option['price'];
				$shipping_options[$i]['price_incl_vat'] = number_format($option['price']*(1+($option['vat_rate']/100)), 2);
			} else {
				$shipping_options[$i]['price_incl_vat'] = $option['price'];
				$shipping_options[$i]['price_excl_vat'] = number_format($option['price']/(1+($option['vat_rate']/100)), 2);
			}
			$shipping_options[$i]['price_vat'] = $shipping_options[$i]['price_incl_vat'] - $shipping_options[$i]['price_excl_vat'];
		}
		
		return $shipping_options;
		
	}
	
}
